package highlife;

/**
 * Main class, reads input and controls evolution steps.
 * @author Trevor Thompson
 */
public class Highlife {

    private Cell[][] grid;

    private static class Cell {
        private boolean alive = false;
        public boolean isAlive() {
            return alive;
        }
        public void setAlive(boolean b) {
            alive = b;
        }
    }

    public void runGame(int numGens, String gridInput) {
        int len = gridInput.length();
        double d = Math.sqrt((double) len);

        //if d is not a whole number, the grid is not square
        if (d % 1 != 0) {
            System.err.println("Error: the supplied grid is not square.");
            return;
        }
        int width = (int) d;

        //populate grid
        grid = new Cell[width][width];
        int idx = 0;
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < width; j++) {
                assert idx < len;
                grid[i][j] = new Cell();
                if (gridInput.charAt(idx) == 'o') {
                    grid[i][j].setAlive(true);
                }
                idx++;
            }
        }

        //run through the generations
        for (int i = 1; i <= numGens; i++) {
            printGrid(i);
            applyRules();
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        if (args.length != 2) {
            System.err.println("Error: you must specify the number of generations and an input grid.");
            return;
        }
        int generations = Integer.parseInt(args[0].trim());
        String input = args[1].trim();
        
        long startTime = System.nanoTime();

        Highlife hl = new Highlife();
        hl.runGame(generations, input);

        long endTime = System.nanoTime();
        int millis = (int) ((endTime - startTime)/1000000);
        System.out.println("Finished in " + millis + " milliseconds.");
    }

    /**
     * print the grid
     * @param i the current generation number
     */
    private void printGrid(int i) {
        System.out.println("Generation " + i);
        for (int k = 0; k < grid.length; k++) {
            for (int j = 0; j < grid[k].length; j++) {
                char c = grid[k][j].isAlive() ? 'o' : '-';
                System.out.print(c);
            }
            System.out.println();
        }
        System.out.println();
    }

    /**
     * advance one generation by applying the rules to each cell
     */
    private void applyRules() {
        Cell[][] newGrid = copyGrid();
        for(int i = 0; i < grid.length; i++) { //for all rows
            for(int j = 0; j < grid[i].length; j++) { //for each column
                int liveNeighbors = 0;
                //grid[i][j] is the cell in question
                for(int k = i - 1; k <= i + 1; k++) { //for R-1 to R+1
                    //don't check indices out of bounds
                    if(k < 0 || k >= grid.length) continue;
                    for(int l = j - 1; l <= j + 1; l++) { //for C-1 to C+1
                        if(l < 0 || l >= grid[k].length) continue;
                        if(k == i && l == j) continue; //don't check the cell itself
                        if(grid[k][l].isAlive()) liveNeighbors++;
                    }
                }
                switch(liveNeighbors) {
                    case 2: //remains unchanged
                        break;
                    case 3:
                    case 6:
                        newGrid[i][j].setAlive(true);
                        break;
                    default:
                        newGrid[i][j].setAlive(false);
                }
            }
        }
        grid = newGrid;
    }

    /**
     * Returns a new copy of the grid to prevent destructive interference when applying the
     * rules. (Changing one cell from alive to dead or vice versa may affect the change for
     * other cells around it. Instead, we want to make all the changes independently.)
     * @return
     */
    private Cell[][] copyGrid() {
        Cell[][] newGrid = new Cell[grid.length][grid.length];
        for(int i = 0; i < newGrid.length; i++){
            for(int j = 0; j < newGrid[i].length; j++) {
                newGrid[i][j] = new Cell();
                newGrid[i][j].setAlive(grid[i][j].isAlive());
            }
        }
        return newGrid;
    }
}
